# -*- coding: utf-8 -*-

# @Time    : 2018/12/31 21:41
# @Author  : Chen
# @File    : input_data.py
# @Software: PyCharm

import os
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

def get_files(file_dir): # 生成图片路径和标签的list
    # file_dir：文件夹路径
    # return：乱序后的图片和标签

    # 函数get_files(file_dir)的功能是获取给定路径file_dir下的所有的训练数据（包括图片和标签），以list的形式返回。
    # 由于训练数据前12500张是猫，后12500张是狗，如果直接按这个顺序训练，训练效果可能会受影响（我自己猜的），所以需要将顺序打乱，至于是读取数据的时候乱序还是训练的时候乱序可以自己选择（视频里说在这里乱序速度比较快）。因为图片和标签是一一对应的，所以要整合到一起乱序。
    # 这里先用np.hstack()方法将猫和狗图片和标签整合到一起，得到image_list和label_list，hstack((a,b))的功能是将a和b以水平的方式连接，比如原来cats和dogs是长度为12500的向量，执行了hstack(cats, dogs)后，image_list的长度为25000，同理label_list的长度也为25000。接着将一一对应的image_list和label_list再合并一次。temp的大小是2×25000，经过转置（变成25000×2），然后使用np.random.shuffle()方法进行乱序。
    # 最后从temp中分别取出乱序后的image_list和label_list列向量，作为函数的返回值。这里要注意，因为label_list里面的数据类型是字符串类型，所以加上label_list = [int(i) for i in label_list]这么一行将其转为int类型。

    cats = []
    label_cats = []
    dogs =[]
    label_dogs = []

    # step1：获取E:\WorkSpace\Python_WorkSpace\learn_tf_dogs-vs-cats\data\dogs-vs-cats\train下所有的猫图路径名，存放到cats中，同时贴上标签0，存放到label_cats中。狗图同理。
    # 载入数据路径并写入标签值
    for file in os.listdir(file_dir):
        name =file.split(sep = '.')
        if name[0] == 'cat':
            cats.append(file_dir + file)
            label_cats.append(0)
        else:
            dogs.append(file_dir + file)
            label_dogs.append(1)
    # print(cats)
    # print(label_cats)
    # print(dogs)
    # print(label_dogs)
    print('There are %d cats.\nThere are %d dogs.' %(len(cats), len(dogs)))

    # step2：对生成的图片路径和标签list做打乱处理
    # 把cat和dog合起来组成一个list（img和lab）
    image_list = np.hstack((cats, dogs)) # cats和dogs的路径串在一起
    # print(image_list)
    label_list = np.hstack((label_cats, label_dogs)) # cats和dogs的标签串在一起
    # print(label_list)

    # 利用shuffle打乱顺序
    temp = np.array([image_list,label_list]) # 路径和标签组成一个数组（路径和标签没有对应）
    # print(temp)
    temp =temp.transpose() # 转置后即可得到一个路径与标签对应的数组
    # print(temp)
    np.random.shuffle(temp) # 打乱数组的顺序
    # print(temp)

    # 从打乱的temp中再取出list（img和lab）
    image_list = list(temp[:, 0]) # 取出打乱后数组的第1列作为取图路径
    # print(image_list)
    label_list = list(temp[:, 1]) # 取出打乱后数据的第2列作为标签
    # print(label_list)
    label_list = [int(i) for i in label_list] # 将标签转为int型
    # print(label_list)

    return image_list, label_list

def get_batch(image, label, image_W, image_H, batch_size, capacity): # 生成Batch
    # image，label：要生成batch的图像和标签list
    # image_W，image_H：设置好固定的图像高度和宽度
    # batch_size：每个batch要放多少张图片
    # capacity：一个队列最大多少（队列容量）
    # return：图像和标签的batch

    # 函数get_batch()用于将图片分批次，因为一次性将所有25000张图片载入内存不现实也不必要，所以将图片分成不同批次进行训练。这里传入的image和label参数就是函数get_files()返回的image_list和label_list，是python中的list类型，所以需要将其转为TensorFlow可以识别的tensor格式。
    # 这里使用队列来获取数据，因为队列操作牵扯到线程，我自己对这块也不懂，，所以只从大体上理解了一下，想要系统学习可以去官方文档看看，这里引用了一张图解释。
    # 我认为大体上可以这么理解：每次训练时，从队列中取一个batch送到网络进行训练，然后又有新的图片从训练库中注入队列，这样循环往复。队列相当于起到了训练库到网络模型间数据管道的作用，训练数据通过队列送入网络。（我也不确定这么理解对不对，欢迎指正）
    # 继续看程序，我们使用slice_input_producer()来建立一个队列，将image和label放入一个list中当做参数传给该函数。然后从队列中取得image和label，要注意，用read_file()读取图片之后，要按照图片格式进行解码。本例程中训练数据是jpg格式的，所以使用decode_jpeg()解码器，如果是其他格式，就要用其他解码器，具体可以从官方API中查询。注意decode出来的数据类型是uint8，之后模型卷积层里面conv2d()要求输入数据为float32类型，所以如果删掉标准化步骤之后需要进行类型转换。
    # 因为训练库中图片大小是不一样的，所以还需要将图片裁剪成相同大小(img_W和img_H)。视频中是用resize_image_with_crop_or_pad()方法来裁剪图片，这种方法是从图像中心向四周裁剪，如果图片超过规定尺寸，最后只会剩中间区域的一部分，可能一只狗只剩下躯干，头都不见了，用这样的图片训练结果肯定会受到影响。所以这里我稍微改动了一下，使用resize_images()对图像进行缩放，而不是裁剪，采用NEAREST_NEIGHBOR插值方法（其他几种插值方法出来的结果图像是花的，具体原因不知道）。
    # 缩放之后视频中还进行了per_image_standardization (标准化)步骤，但加了这步之后，得到的图片是花的，虽然各个通道单独提出来是正常的，三通道一起就不对了，删了标准化这步结果正常，所以这里把标准化步骤注释掉了。
    # 然后用tf.train.batch()方法获取batch，还有一种方法是tf.train.shuffle_batch()，因为之前我们已经乱序过了，这里用普通的batch()就好。视频中获取batch后还对label进行了一下reshape()操作，在我看来这步是多余的，从batch()方法中获取的大小已经符合我们的要求了，注释掉也没什么影响，能正常获取图片。
    # 最后将得到的image_batch和label_batch返回。image_batch是一个4D的tensor，[batch, width, height, channels]，label_batch是一个1D的tensor，[batch]。
    # 可以用下面的代码测试获取图片是否成功，因为之前将图片转为float32了，因此这里imshow()出来的图片色彩会有点奇怪，因为本来imshow()是显示uint8类型的数据（灰度值在uint8类型下是0~255，转为float32后会超出这个范围，所以色彩有点奇怪），不过这不影响后面模型的训练。

    # step1：将上面生成的List传入get_batch() ，转换类型，产生一个输入队列queue，因为img和lab是分开的，所以使用tf.train.slice_input_producer()，然后用tf.read_file()从队列中读取图像
    # 转换类型，将python.list类型转换成tf能够识别的格式
    image = tf.cast(image, tf.string) # tf.cast(x, dtype, name=None) 将x的数据格式转化成dtype数据类型。例如，原来x的数据格式是bool，那么将其转化成float以后，就能够将其转化成0和1的序列。
    label = tf.cast(label, tf.int32)

    # 生成队列（make an input queue）
    input_queue = tf.train.slice_input_producer([image, label]) # tf.train.slice_input_producer参考https://blog.csdn.net/dcrmg/article/details/79776876

    label = input_queue[1]
    image_contents = tf.read_file(input_queue[0]) # read img from a queue，tf.read_file为Tensorflow从文件读取图片的方法，参考https://www.jb51.net/article/134550.htm

    # step2：将图像解码，不同类型的图像不能混在一起，要么只用jpeg，要么只用png等。
    image = tf.image.decode_jpeg(image_contents, channels = 3) # decode_jpeg函数为jpeg（jpg）图片解码的过程，对应的encode_jpeg函数为编码过程，编码后将图片重命名写入到指定的路径下。
    # tensorflow里面给出了一个函数用来读取图像，不过得到的结果是最原始的图像，是没有经过解码的图像，这个函数为tf.gfile.FastGFile（‘path’，‘r’）.read()。如果要显示读入的图像，那就需要经过解码过程，tensorflow里面提供解码的函数有两个，tf.image.decode_jepg和tf.image.decode_png分别用于解码jpg格式和png格式的图像进行解码，得到图像的像素值，这个像素值可以用于显示图像。如果没有解码，读取的图像是一个字符串，没法显示。

    # step3：数据预处理，对图像进行旋转、缩放、裁剪、归一化等操作，让计算出的模型更健壮。
    image = tf.image.resize_image_with_crop_or_pad(image, image_W, image_H)
    # 图像尺寸调整属于基础的图像几何变换，TensorFlow提供了几种尺寸调整的函数：
    # tf.image.resize_images：将原始图像缩放成指定的图像大小，其中的参数method（默认值为ResizeMethod.BILINEAR）提供了四种插值算法，具体解释可以参考图像几何变换（缩放、旋转）中的常用的插值算法
    # tf.image.resize_image_with_crop_or_pad：剪裁或填充处理，会根据原图像的尺寸和指定的目标图像的尺寸选择剪裁还是填充，如果原图像尺寸大于目标图像尺寸，则在中心位置剪裁，反之则用黑色像素填充。
    # tf.image.central_crop：比例调整，central_fraction决定了要指定的比例，取值范围为(0，1]，该函数会以中心点作为基准，选择整幅图中的指定比例的图像作为新的图像。

    image = tf.image.per_image_standardization(image) # tf.image.per_image_standardization(image)将整幅图片标准化
    # 标准化处理可以使得不同的特征具有相同的尺度（Scale）。这样，在使用梯度下降法学习参数的时候，不同特征对参数的影响程度就一样了。tf.image.per_image_standardization(image)，此函数的运算过程是将整幅图片标准化（不是归一化），加速神经网络的训练。主要有如下操作，(x - mean) / adjusted_stddev，其中x为图片的RGB三通道像素值，mean分别为三通道像素的均值，adjusted_stddev = max(stddev, 1.0/sqrt(image.NumElements()))。stddev为三通道像素的标准差，image.NumElements()计算的是三通道各自的像素个数。

    # step4：生成batch
    image_batch, label_batch = tf.train.batch([image, label], batch_size = batch_size, num_threads = 32, capacity = capacity)
    # image_batch: 4D tensor [batch_size, width, height, 3]，dtype=tf.float32
    # label_batch: 1D tensor [batch_size], dtype=tf.int32

    # 重新排列label，行数为[batch_size]
    label_batch = tf.reshape(label_batch, [batch_size]) # reshape(tensor, shape, name=None)，第1个参数为被调整维度的张量。第2个参数为要调整为的形状。返回一个shape形状的新tensor，注意shape里最多有一个维度的值可以填写为-1，表示自动计算此维度。
    # 转换image_batch的类型为tf.float32
    image_batch = tf.cast(image_batch, tf.float32) # tf.cast(x, dtype, name=None) 将x的数据格式转化成dtype数据类型。例如，原来x的数据格式是bool，那么将其转化成float以后，就能够将其转化成0和1的序列。

    return image_batch, label_batch

# if __name__ == '__main__':
#     # file_dir = '..\\data\\dogs-vs-cats\\train\\'
#     # get_files(file_dir)
#
#     # 测试1
#     # step1：变量初始化，每批2张图，尺寸208x208，设置好自己的图像路径
#     BATCH_SIZE = 2
#     CAPACITY = 256
#     IMG_W = 208
#     IMG_H = 208
#
#     train_dir = '..\\data\\dogs-vs-cats\\train\\'
#
#     # step2：调用前面的两个函数，生成batch
#     image_list, label_list = get_files(train_dir)
#     image_batch, label_batch = get_batch(image_list, label_list, IMG_W, IMG_H, BATCH_SIZE, CAPACITY)
#
#     # step3：开启会话session，利用tf.train.Coordinator()和tf.train.start_queue_runners(coord=coord)来监控队列（这里有个问题：官网的start_queue_runners()是有两个参数的，sess和coord，但是在这里加上sess的话会报错）。利用try——except——finally结构来执行队列操作（官网推荐的方法），避免程序卡死什么的。i < 2执行两次队列操作，每一次取出2张图放进batch里面，然后imshow出来看看效果。
#     with tf.Session() as sess:
#         i = 0
#         coord = tf.train.Coordinator()
#         threads = tf.train.start_queue_runners(coord = coord)
#
#         try:
#             while not coord.should_stop() and i < 2:
#
#                 img, label = sess.run([image_batch, label_batch])
#
#                 # just test one batch
#                 for j in np.arange(BATCH_SIZE):
#                     print('label: %d' % label[j])
#                     plt.imshow(img[j, :, :, :])
#                     plt.show()
#                 i += 1
#
#         except tf.errors.OutOfRangeError:
#             print('done!')
#         finally:
#             coord.request_stop()
#         coord.join(threads)